From b33c59a1b0ad90507504df5d783562536a2a2574 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Sat, 11 Mar 2023 11:14:29 -0700 Subject: [PATCH] enhance exif subsecond and offset handling. (#1033) * enhance exif subsecond and offset handling. * don't warn for legal empty offset tags. --- exif.cc | 32 +++++++++++++++++++++----- exif.h | 4 +++- reference/format3.txt | 2 ++ reference/help.txt | 1 + xmldoc/formats/options/exif-frame.xml | 8 ++----- xmldoc/formats/options/exif-offset.xml | 10 ++++++++ 6 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 xmldoc/formats/options/exif-offset.xml diff --git a/exif.cc b/exif.cc index 77fa91622..da634db51 100644 --- a/exif.cc +++ b/exif.cc @@ -677,7 +677,7 @@ ExifFormat::exif_find_tag(ExifApp* app, const uint16_t ifd_nr, const uint16_t ta } QDateTime -ExifFormat::exif_get_exif_time(ExifApp* app) +ExifFormat::exif_get_exif_time(ExifApp* app) const { QDateTime res; @@ -700,20 +700,24 @@ ExifFormat::exif_get_exif_time(ExifApp* app) // Exif 2.31 added offset tags to record the offset to UTC. // If these are present use them, otherwise assume local time. ExifTag* offset_tag = nullptr; + ExifTag* subsec_tag = nullptr; switch (tag->id) { case 0x9003: offset_tag = exif_find_tag(app, EXIF_IFD, 0x9011); /* OffsetTimeOriginal from EXIF */ + subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9291); /* SubSecTimeOriginal from EXIF */ break; case 0x0132: offset_tag = exif_find_tag(app, EXIF_IFD, 0x9010); /* OffsetTime from EXIF */ + subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9290); /* SubSecTime from EXIF */ break; case 0x9004: offset_tag = exif_find_tag(app, EXIF_IFD, 0x9012); /* OffsetTimeDigitized from EXIF */ + subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9292); /* SubSecTimeDigitized from EXIF */ break; } - if (offset_tag) { - QByteArray time_tag = exif_read_str(offset_tag); + if (offset_tag || opt_offsettime) { + QByteArray time_tag = opt_offsettime? QByteArray(opt_offsettime) : exif_read_str(offset_tag); // string should be +HH:MM or -HH:MM static const QRegularExpression re(R"(^([+-])(\d{2}):(\d{2})$)"); assert(re.isValid()); @@ -723,10 +727,24 @@ ExifFormat::exif_get_exif_time(ExifApp* app) int offset_hours = match.captured(1).append(match.captured(2)).toInt(); int offset_mins = match.captured(1).append(match.captured(3)).toInt(); res.setOffsetFromUtc(((offset_hours * 60) + offset_mins) * 60); + } else if (opt_offsettime) { + // Only warn for user supplied offsets. + // Offset tags may indicate the offset was unknown, e.g. " : ". + warning(MYNAME ": OffsetTime is expected to be +HH:MM or -HH:MM, but was %s.\n", time_tag.constData()); } } + + if (subsec_tag) { + QByteArray subsec = exif_read_str(subsec_tag); + bool ok; + double ss = subsec.prepend("0.").toDouble(&ok); + if (ok) { + res = res.addMSecs(lround(1000.0 * ss)); + } + } + } - return res; + return res.toUTC(); } Waypoint* @@ -895,7 +913,7 @@ ExifFormat::exif_waypt_from_exif_app(ExifApp* app) const gps_datetime = QDateTime(datestamp, timestamp, Qt::UTC); if (gps_datetime.isValid()) { if (global_opts.debug_level >= 3) { - printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODate))); + printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODateWithMs))); } wpt->SetCreationTime(gps_datetime); } else { @@ -1555,7 +1573,9 @@ ExifFormat::write() exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 0, dt.time().hour()); exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 1, dt.time().minute()); - exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, dt.time().second()); + exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, + static_cast(dt.time().second()) + + static_cast(dt.time().msec())/1000.0); exif_put_str(GPS_IFD, GPS_IFD_TAG_DATESTAMP, CSTR(dt.toString(u"yyyy:MM:dd"))); } else { diff --git a/exif.h b/exif.h index a706021f1..3c0a9f85e 100644 --- a/exif.h +++ b/exif.h @@ -172,7 +172,7 @@ private: static void exif_examine_app(ExifApp* app); static ExifIfd* exif_find_ifd(ExifApp* app, uint16_t ifd_nr); static ExifTag* exif_find_tag(ExifApp* app, uint16_t ifd_nr, uint16_t tag_id); - static QDateTime exif_get_exif_time(ExifApp* app); + QDateTime exif_get_exif_time(ExifApp* app) const; Waypoint* exif_waypt_from_exif_app(ExifApp* app) const; static Rational exif_dec2frac(double val, double tolerance); ExifTag* exif_put_value(int ifd_nr, uint16_t tag_id, uint16_t type, int count, int index, const void* data) const; @@ -205,12 +205,14 @@ private: char* opt_overwrite{}; char* opt_frame{}; char* opt_name{}; + char* opt_offsettime{}; QVector exif_args = { { "filename", &opt_filename, "Set waypoint name to source filename", "Y", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr }, { "frame", &opt_frame, "Time-frame (in seconds)", "10", ARGTYPE_INT, "0", nullptr, nullptr }, { "name", &opt_name, "Locate waypoint for tagging by this name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, { "overwrite", &opt_overwrite, "!OVERWRITE! the original file. Default=N", "N", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr }, + { "offset", &opt_offsettime, "Image Offset Time (+HH:MM or -HH:MM)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, }; }; #endif // EXIF_H_INCLUDED_ diff --git a/reference/format3.txt b/reference/format3.txt index ab9e479ec..fe814dd92 100644 --- a/reference/format3.txt +++ b/reference/format3.txt @@ -126,6 +126,8 @@ option exif name Locate waypoint for tagging by this name string https://www. option exif overwrite !OVERWRITE! the original file. Default=N boolean N https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_overwrite +option exif offset Image Offset Time (+HH:MM or -HH:MM) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_offset + file rwrwrw shape shp ESRI shapefile shape https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html option shape name Source for name field in .dbf string 0 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html#fmt_shape_o_name diff --git a/reference/help.txt b/reference/help.txt index 715be12bd..e118164f5 100644 --- a/reference/help.txt +++ b/reference/help.txt @@ -71,6 +71,7 @@ File Types (-i and -o options): frame Time-frame (in seconds) name Locate waypoint for tagging by this name overwrite (0/1) !OVERWRITE! the original file. Default=N + offset Image Offset Time (+HH:MM or -HH:MM) shape ESRI shapefile name Source for name field in .dbf url Source for URL field in .dbf diff --git a/xmldoc/formats/options/exif-frame.xml b/xmldoc/formats/options/exif-frame.xml index ac71f3259..946976c5b 100644 --- a/xmldoc/formats/options/exif-frame.xml +++ b/xmldoc/formats/options/exif-frame.xml @@ -7,10 +7,6 @@ gpsbabel -i gpx -f holiday.gpx -o exif,frame=60 -F IMG0784.JPG - If the camera time wasn't adjusted, you should move the track(s) by the this difference. - I.e. if the camera time is five minutes behind your time, the track(s) should be shifted - five minutes back. - - - gpsbabel -i gpx -f holiday.gpx -x track,move=-5m -o exif,frame=60 -F IMG0784.JPG + If the camera time wasn't adjusted, you should use the offset option. You may also need to use the frame option, or + the interpolate filter. diff --git a/xmldoc/formats/options/exif-offset.xml b/xmldoc/formats/options/exif-offset.xml new file mode 100644 index 000000000..e76d48ba3 --- /dev/null +++ b/xmldoc/formats/options/exif-offset.xml @@ -0,0 +1,10 @@ + + Uses the given value instead of the value from the tag OffsetTime, OffsetTimeOriginal or OffsetTimeDigitized. + This is useful when the image doesn't contain an OffsetTime* tag and the offset is different from the local time, or when the image contains a tag that is incorrect. + The format of this option should match that of the tag OffsetTime*, specifcally it must be "+HH:MM" or "-HH:MM". + + + If the camera was using China Standard Time, e.g. in the winter in Taiwan, then you should supply + an offset of "+8:00". +gpsbabel -i gpx -f holiday.gpx -o exif,offset=+08:00 -F IMG0784.JPG + -- 2.30.2